package com.im.project.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.im.common.constant.Constant;
import com.im.common.enums.MessageTypeEnum;
import com.im.common.enums.ResultEnum;
import com.im.common.enums.WsEnum;
import com.im.common.exception.BusinessException;
import com.im.common.util.GenerateIdUtil;
import com.im.common.util.StringTool;
import com.im.project.entity.*;
import com.im.project.entity.dto.ContactApplyDto;
import com.im.project.entity.dto.DeleteContactDto;
import com.im.project.entity.vo.*;
import com.im.project.mapper.*;
import com.im.project.rabbitmq.RabbitmqProducer;
import com.im.project.service.ContactService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 联系人 服务层接口实现类
 *
 * @author yunZhouJiaDi
 */
@Slf4j
@Service
public class ContactServiceImpl extends ServiceImpl<ContactMapper, Contact> implements ContactService {

    @Resource
    private ContactMapper contactMapper;

    @Resource
    private UserMapper userMapper;

    @Resource
    private ContactApplyMapper contactApplyMapper;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private ChatMapper chatMapper;

    @Resource
    private ChatUserMapper chatUserMapper;

    @Resource
    private ChatMessageMapper chatMessageMapper;

    @Resource
    private ClusterMapper clusterMapper;

    @Resource
    private ClusterUserMapper clusterUserMapper;

    @Resource
    private RabbitmqProducer rabbitmqProducer;

    @Override
    public List<SearchContactVo> queryContact(String searchKey) {
        if (!StringTool.isEmpty(searchKey)) {
            List<User> list = userMapper.selectList(new QueryWrapper<User>()
                    .like("user_id", searchKey)
                    .or()
                    .like("username", searchKey));
            List<SearchContactVo> arrayList = new ArrayList<>();
            list.forEach(user -> {
                SearchContactVo searchContactVo = new SearchContactVo().
                        setContactId(user.getUserId()).
                        setContactName(user.getUsername()).
                        setContactIcon(user.getIcon()).
                        setSex(user.getSex()).
                        setAreaName(user.getAreaName());
                Contact contact = contactMapper.selectOne(
                        new QueryWrapper<Contact>().
                                eq("user_id", StpUtil.getLoginIdAsString()).
                                eq("contact_id", user.getUserId()));
                if (!ObjectUtils.isEmpty(contact)) {
                    searchContactVo.setStatus(contact.getStatus());
                }
                arrayList.add(searchContactVo);
            });
            return arrayList;
        } else {
            return new ArrayList<SearchContactVo>();
        }
    }

    @Override
    @Transactional
    public Boolean apply(ContactApplyDto contactApplyDto) {
        UserVo currentUser = (UserVo) redisTemplate.opsForValue().get(Constant.RedisConstant.USER + StpUtil.getLoginIdAsString());
        String applyInfo = StringTool.isEmpty(contactApplyDto.getApplyInfo()) ? String.format(Constant.SystemConstant.APPLY_INFO_TEMPLATE, currentUser.getUsername()) : contactApplyDto.getApplyInfo();
        String prefix = contactApplyDto.getContactId().substring(0, 1);
        Date date = new Date();
        switch (prefix) {
            case Constant.SystemConstant.USER_PREFIX:
                Contact contact = contactMapper.selectOne(new QueryWrapper<Contact>().eq("user_id", StpUtil.getLoginIdAsString()).eq("contact_id", contactApplyDto.getContactId()));
                if (!ObjectUtils.isEmpty(contact)) {
                    if (contact.getStatus() == ResultEnum.QUILT_BLOCK_FRIEND_CONTACT_STATUS.getCode()) {
                        throw new BusinessException(ResultEnum.QUILT_BLOCK_FRIEND_CONTACT_STATUS.getCode(), ResultEnum.QUILT_BLOCK_FRIEND_CONTACT_STATUS.getMessage());
                    }
                    if (contact.getStatus() == ResultEnum.FRIEND_CONTACT_STATUS.getCode()) {
                        throw new BusinessException(ResultEnum.FRIEND_CONTACT_STATUS.getCode(), ResultEnum.FRIEND_CONTACT_STATUS.getMessage());
                    }
                }
                UserVo contactUser = (UserVo) redisTemplate.opsForValue().get(Constant.RedisConstant.USER + contactApplyDto.getContactId());
                if (contactUser.getJoinType() == ResultEnum.DIRECTLY_JOIN_TYPE.getCode()) {
                    String chatId = GenerateIdUtil.generateUserChatId(new String[]{StpUtil.getLoginIdAsString(), contactApplyDto.getContactId()});
                    ChatMessage chatMessage = new ChatMessage().
                            setChatId(chatId).
                            setMessageId(GenerateIdUtil.generateMessageId()).
                            setMessageType(MessageTypeEnum.ADD.getName()).
                            setMessageContent(applyInfo).
                            setContactType(ResultEnum.FRIEND_TYPE.getCode()).
                            setSendUserId(StpUtil.getLoginIdAsString()).
                            setSendUsername(currentUser.getUsername()).
                            setSendUserIcon(currentUser.getIcon()).
                            setSendTime(date.getTime()).
                            setContactId(contactApplyDto.getContactId()).
                            setContactUsername(contactUser.getUsername()).
                            setContactUserIcon(contactUser.getIcon());
                    contactMapper.delete(
                            new QueryWrapper<Contact>().
                                    eq("user_id", StpUtil.getLoginIdAsString()).
                                    eq("contact_id", contactApplyDto.getContactId()).
                                    ne("status", ResultEnum.FRIEND_CONTACT_STATUS.getCode())
                    );
                    contactMapper.delete(
                            new QueryWrapper<Contact>().
                                    eq("user_id", contactApplyDto.getContactId()).
                                    eq("contact_id", StpUtil.getLoginIdAsString()).
                                    ne("status", ResultEnum.FRIEND_CONTACT_STATUS.getCode())
                    );
                    contactMapper.insert(
                            new Contact().
                                    setUserId(StpUtil.getLoginIdAsString()).
                                    setContactId(contactApplyDto.getContactId()).
                                    setCreateTime(date).
                                    setStatus(ResultEnum.FRIEND_CONTACT_STATUS.getCode()).
                                    setLastUpdateTime(date)
                    );
                    redisTemplate.opsForList().leftPush(Constant.RedisConstant.USER_CONTACT + StpUtil.getLoginIdAsString(), contactApplyDto.getContactId());
                    contactMapper.insert(
                            new Contact().
                                    setUserId(contactApplyDto.getContactId()).
                                    setContactId(StpUtil.getLoginIdAsString()).
                                    setCreateTime(date).
                                    setStatus(ResultEnum.FRIEND_CONTACT_STATUS.getCode()).
                                    setLastUpdateTime(date)
                    );
                    redisTemplate.opsForList().leftPush(Constant.RedisConstant.USER_CONTACT + contactApplyDto.getContactId(), StpUtil.getLoginIdAsString());
                    chatMapper.delete(new QueryWrapper<Chat>().eq("chat_id", chatId));
                    chatUserMapper.delete(
                            new QueryWrapper<ChatUser>().
                                    eq("chat_id", chatId).
                                    eq("user_id", StpUtil.getLoginIdAsString()).
                                    eq("contact_id", contactApplyDto.getContactId())
                    );
                    chatUserMapper.delete(
                            new QueryWrapper<ChatUser>().
                                    eq("chat_id", chatId).
                                    eq("user_id", contactApplyDto.getContactId()).
                                    eq("contact_id", StpUtil.getLoginIdAsString())
                    );
                    chatMapper.insert(
                            new Chat().
                                    setChatId(chatId).
                                    setLastMessage(applyInfo).
                                    setLastReceiveTime(date.getTime())
                    );
                    chatUserMapper.insert(
                            new ChatUser().
                                    setChatId(chatId).
                                    setUserId(StpUtil.getLoginIdAsString()).
                                    setContactId(contactApplyDto.getContactId()).
                                    setContactName(contactUser.getUsername())
                    );
                    chatUserMapper.insert(
                            new ChatUser().
                                    setChatId(chatId).
                                    setUserId(contactApplyDto.getContactId()).
                                    setContactId(StpUtil.getLoginIdAsString()).
                                    setContactName(currentUser.getUsername())
                    );
                    chatMessageMapper.insert(chatMessage);
                    rabbitmqProducer.sendMessageProducer(
                            Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                            new MessageVo<ChatMessage>().
                                    setCommandCode(WsEnum.ADD_FRIEND.getCode()).
                                    setCommandName(WsEnum.ADD_FRIEND.getMessage()).
                                    setContactId(StpUtil.getLoginIdAsString()).
                                    setData(chatMessage)
                    );
                    rabbitmqProducer.sendMessageProducer(
                            Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                            new MessageVo<ChatMessage>().
                                    setCommandCode(WsEnum.ADD_FRIEND_SELF.getCode()).
                                    setCommandName(WsEnum.ADD_FRIEND_SELF.getMessage()).
                                    setContactId(contactApplyDto.getContactId()).
                                    setData(chatMessage)
                    );
                } else if (contactUser.getJoinType() == ResultEnum.AGREED_JOIN_TYPE.getCode()) {
                    ContactApply contactApply = contactApplyMapper.selectOne(new QueryWrapper<ContactApply>().
                            eq("apply_user_id", StpUtil.getLoginIdAsString()).
                            eq("receive_user_id", contactApplyDto.getContactId()).
                            eq("contact_id", contactApplyDto.getContactId()));
                    if (ObjectUtils.isEmpty(contactApply)) {
                        contactApplyMapper.insert(
                                new ContactApply().
                                        setApplyUserId(StpUtil.getLoginIdAsString()).
                                        setReceiveUserId(contactApplyDto.getContactId()).
                                        setContactId(contactApplyDto.getContactId()).
                                        setContactType(ResultEnum.FRIEND_TYPE.getCode()).
                                        setLastApplyTime(date.getTime()).
                                        setApplyInfo(contactApplyDto.getApplyInfo()).
                                        setStatus(ResultEnum.PEND_CONTACT_APPLY_STATUS.getCode())
                        );
                    } else {
                        contactApply.
                                setStatus(ResultEnum.PEND_CONTACT_APPLY_STATUS.getCode()).
                                setLastApplyTime(date.getTime()).
                                setApplyInfo(contactApplyDto.getApplyInfo());
                        contactApplyMapper.updateById(contactApply);
                    }
                    rabbitmqProducer.sendMessageProducer(
                            Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                            new MessageVo<>().
                                    setCommandCode(WsEnum.CONTACT_APPLY.getCode()).
                                    setCommandName(WsEnum.CONTACT_APPLY.getMessage()).
                                    setContactId(contactApplyDto.getContactId()).
                                    setData(null)
                    );
                }
                break;
            case Constant.SystemConstant.CLUSTER_PREFIX:
                Cluster cluster = null;
                cluster = (Cluster) redisTemplate.opsForValue().get(Constant.RedisConstant.CLUSTER + contactApplyDto.getContactId());
                if (ObjectUtils.isEmpty(cluster)) {
                    cluster = clusterMapper.selectOne(new QueryWrapper<Cluster>().eq("cluster_id", contactApplyDto.getContactId()));
                    redisTemplate.opsForValue().set(Constant.RedisConstant.CLUSTER + contactApplyDto.getContactId(), cluster);
                }
                if (cluster.getStatus() == ResultEnum.CLUSTER_DISSOLUTION.getCode()) {
                    throw new BusinessException(ResultEnum.CLUSTER_DISSOLUTION.getCode(), ResultEnum.CLUSTER_DISSOLUTION.getMessage());
                }
                List<String> contactIdList = redisTemplate.opsForList().range(Constant.RedisConstant.CLUSTER_USER + cluster.getClusterId(), 0, -1);
                if (contactIdList.contains(StpUtil.getLoginIdAsString())) {
                    throw new BusinessException(ResultEnum.YOU_ARE_IN_THE_GROUP.getCode(), ResultEnum.YOU_ARE_IN_THE_GROUP.getMessage());
                }
                if (cluster.getJoinType() == ResultEnum.DIRECTLY_JOIN_TYPE.getCode()) {
                    ChatUser chatUser = chatUserMapper.selectOne(new QueryWrapper<ChatUser>().eq("user_id", StpUtil.getLoginIdAsString()).eq("contact_id", contactApplyDto.getContactId()));
                    clusterUserMapper.insert(
                            new ClusterUser().
                                    setClusterId(contactApplyDto.getContactId()).
                                    setUserId(StpUtil.getLoginIdAsString()).
                                    setRole(ResultEnum.ORDINARY_MEMBERS.getCode()).
                                    setCreateTime(date)
                    );
                    redisTemplate.opsForList().leftPush(Constant.RedisConstant.CLUSTER_USER + contactApplyDto.getContactId(), StpUtil.getLoginIdAsString());
                    String chatId = GenerateIdUtil.generateClusterChatId(contactApplyDto.getContactId());
                    chatMapper.update(
                            new Chat().setLastReceiveTime(date.getTime()).setLastSendName("").setLastMessage(String.format(Constant.SystemConstant.ADD_CLUSTER, '`' + currentUser.getUsername() + '`' + " ")),
                            new QueryWrapper<Chat>().eq("chat_id", chatId)
                    );
                    if (ObjectUtils.isEmpty(chatUser)) {
                        chatUserMapper.insert(new ChatUser().
                                setChatId(chatId).
                                setUserId(StpUtil.getLoginIdAsString()).
                                setContactId(contactApplyDto.getContactId()).
                                setContactName(cluster.getName()));
                    } else {
                        chatUserMapper.updateById(chatUser.
                                setChatId(chatId).
                                setUserId(StpUtil.getLoginIdAsString()).
                                setContactId(contactApplyDto.getContactId()).
                                setContactName(cluster.getName()));
                    }
                    ChatMessage chatMessage = new ChatMessage().
                            setChatId(chatId).
                            setMessageId(GenerateIdUtil.generateMessageId()).
                            setMessageType(MessageTypeEnum.COMMON.getName()).
                            setMessageContent(String.format(Constant.SystemConstant.ADD_CLUSTER, currentUser.getUsername())).
                            setContactType(ResultEnum.CLUSTER_TYPE.getCode()).
                            setSendUserId(StpUtil.getLoginIdAsString()).
                            setSendUsername(currentUser.getUsername()).
                            setSendUserIcon(currentUser.getIcon()).
                            setSendTime(date.getTime()).
                            setContactId(contactApplyDto.getContactId()).
                            setContactUsername(cluster.getName()).
                            setContactUserIcon(cluster.getIcon());
                    chatMessageMapper.insert(chatMessage);
                    chatMessage.setMemberCount(contactIdList.size());
                    contactIdList.forEach(contactId -> {
                        rabbitmqProducer.sendMessageProducer(
                                Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                                new MessageVo<ChatMessage>().
                                        setCommandCode(WsEnum.ADD_FRIEND.getCode()).
                                        setCommandName(WsEnum.ADD_FRIEND.getMessage()).
                                        setContactId(contactId).
                                        setData(chatMessage)
                        );
                    });
                } else if (cluster.getJoinType() == ResultEnum.AGREED_JOIN_TYPE.getCode()) {
                    ContactApply contactApply = contactApplyMapper.selectOne(new QueryWrapper<ContactApply>().
                            eq("apply_user_id", StpUtil.getLoginIdAsString()).
                            eq("receive_user_id", cluster.getOwnerId()).
                            eq("contact_id", contactApplyDto.getContactId()));
                    if (ObjectUtils.isEmpty(contactApply)) {
                        contactApplyMapper.insert(
                                new ContactApply().
                                        setApplyUserId(StpUtil.getLoginIdAsString()).
                                        setReceiveUserId(cluster.getOwnerId()).
                                        setContactId(contactApplyDto.getContactId()).
                                        setContactType(ResultEnum.CLUSTER_TYPE.getCode()).
                                        setLastApplyTime(new Date().getTime()).
                                        setApplyInfo(contactApplyDto.getApplyInfo()).
                                        setStatus(ResultEnum.PEND_CONTACT_APPLY_STATUS.getCode())
                        );
                    } else {
                        contactApply.
                                setStatus(ResultEnum.PEND_CONTACT_APPLY_STATUS.getCode()).
                                setLastApplyTime(new Date().getTime()).
                                setApplyInfo(contactApplyDto.getApplyInfo());
                        contactApplyMapper.updateById(contactApply);
                    }
                    rabbitmqProducer.sendMessageProducer(
                            Constant.RabbitmqConstant.ROUTING_KEY + Constant.RabbitmqConstant.SEND_MESSAGE,
                            new MessageVo<>().
                                    setCommandCode(WsEnum.CONTACT_APPLY.getCode()).
                                    setCommandName(WsEnum.CONTACT_APPLY.getMessage()).
                                    setContactId(cluster.getOwnerId()).
                                    setData(null)
                    );
                }
                break;
        }
        return true;
    }

    @Override
    public List<ContactVo> queryUserContactList() {
        return contactMapper.queryUserContactList(StpUtil.getLoginIdAsString());
    }

    @Override
    public ContactUserInfoVo queryContactUserInfo(String contactId) {
        ContactUserInfoVo contactUserInfoVo = new ContactUserInfoVo();
        Contact contact = contactMapper.selectOne(new QueryWrapper<Contact>().eq("user_id", StpUtil.getLoginIdAsString()).eq("contact_id", contactId));
        if (ObjectUtils.isEmpty(contact)) {
            throw new BusinessException(ResultEnum.NOT_YOUR_FRIEND.getCode(), ResultEnum.NOT_YOUR_FRIEND.getMessage());
        } else {
            User user = userMapper.selectOne(new QueryWrapper<User>().eq("user_id", contactId));
            contactUserInfoVo.
                    setContactId(contactId).
                    setContactName(user.getUsername()).
                    setStatus(user.getStatus()).
                    setContactIcon(user.getIcon()).
                    setSex(user.getSex()).
                    setAreaName(user.getAreaName()).
                    setNote(user.getNote());
        }
        return contactUserInfoVo;
    }

    @Override
    public ContactUserInfoVo queryContactInfo(String contactId) {
        User user = userMapper.selectOne(new QueryWrapper<User>().eq("user_id", contactId));
        ContactUserInfoVo contactUserInfoVo = new ContactUserInfoVo().
                setContactName(user.getUsername()).
                setContactIcon(user.getIcon()).
                setSex(user.getSex()).
                setAreaName(user.getAreaName()).
                setContactId(contactId);
        Contact contact = contactMapper.selectOne(new QueryWrapper<Contact>().eq("user_id", StpUtil.getLoginIdAsString()).eq("contact_id", contactId));
        if (ObjectUtils.isEmpty(contact)) {
            contactUserInfoVo.setStatus(ResultEnum.NO_FRIEND_CONTACT_STATUS.getCode());
        } else {
            contactUserInfoVo.setStatus(contact.getStatus());
        }
        return contactUserInfoVo;
    }

    @Override
    @Transactional
    public Boolean deleteContact(DeleteContactDto deleteContactDto) {
        contactMapper.update(
                new Contact().setStatus(ResultEnum.DELETE_FRIEND_CONTACT_STATUS.getCode()),
                new QueryWrapper<Contact>().eq("user_id", StpUtil.getLoginIdAsString()).eq("contact_id", deleteContactDto.getContactId())
        );
        redisTemplate.opsForList().remove(Constant.RedisConstant.USER_CONTACT + StpUtil.getLoginIdAsString(), 1, deleteContactDto.getContactId());
        String chatId = GenerateIdUtil.generateUserChatId(new String[]{StpUtil.getLoginIdAsString(), deleteContactDto.getContactId()});
        chatUserMapper.delete(
                new QueryWrapper<ChatUser>().
                        eq("chat_id", chatId).
                        eq("user_id", StpUtil.getLoginIdAsString()).
                        eq("contact_id", deleteContactDto.getContactId()));
        contactMapper.update(
                new Contact().setStatus(ResultEnum.QUILT_DELETE_FRIEND_CONTACT_STATUS.getCode()),
                new QueryWrapper<Contact>().eq("user_id", deleteContactDto.getContactId()).eq("contact_id", StpUtil.getLoginIdAsString())
        );
        redisTemplate.opsForList().remove(Constant.RedisConstant.USER_CONTACT + deleteContactDto.getContactId(), 1, StpUtil.getLoginIdAsString());
        return true;
    }

    @Override
    @Transactional
    public Boolean blackedContact(DeleteContactDto deleteContactDto) {
        contactMapper.update(
                new Contact().setStatus(ResultEnum.BLOCK_FRIEND_CONTACT_STATUS.getCode()),
                new QueryWrapper<Contact>().eq("user_id", StpUtil.getLoginIdAsString()).eq("contact_id", deleteContactDto.getContactId())
        );
        redisTemplate.opsForList().remove(Constant.RedisConstant.USER_CONTACT + StpUtil.getLoginIdAsString(), 1, deleteContactDto.getContactId());
        String chatId = GenerateIdUtil.generateUserChatId(new String[]{StpUtil.getLoginIdAsString(), deleteContactDto.getContactId()});
        chatUserMapper.delete(
                new QueryWrapper<ChatUser>().
                        eq("chat_id", chatId).
                        eq("user_id", StpUtil.getLoginIdAsString()).
                        eq("contact_id", deleteContactDto.getContactId()));
        contactMapper.update(
                new Contact().setStatus(ResultEnum.QUILT_BLOCK_FRIEND_CONTACT_STATUS.getCode()),
                new QueryWrapper<Contact>().eq("user_id", deleteContactDto.getContactId()).eq("contact_id", StpUtil.getLoginIdAsString())
        );
        redisTemplate.opsForList().remove(Constant.RedisConstant.USER_CONTACT + deleteContactDto.getContactId(), 1, StpUtil.getLoginIdAsString());
        return true;
    }

}
